package ink.fujisann.learning.schedule;

import cn.jiguang.common.ClientConfig;
import cn.jiguang.common.resp.APIConnectionException;
import cn.jiguang.common.resp.APIRequestException;
import cn.jpush.api.JPushClient;
import cn.jpush.api.push.PushResult;
import cn.jpush.api.push.model.Platform;
import cn.jpush.api.push.model.PushPayload;
import cn.jpush.api.push.model.audience.Audience;
import cn.jpush.api.push.model.notification.Notification;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import ink.fujisann.learning.schedule.dto.CoinCal;
import ink.fujisann.learning.schedule.dto.CoinInfo;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
public class BitCoinCal {

    @Scheduled(cron = "0 13/15 * * * ?")
    public void cal() {
        log.info("开始执行定时任务");
        calWrapper();
        log.info("定时任务执行完毕");
    }

    /**
     * 极光推送消息到app
     *
     * @param msg 消息内容
     */
    private void push2App(String msg) {
        String MASTER_SECRET = "7dbd729063883ca7d03eec1b";
        String APP_KEY = "9c14d2e99e36106cf18f35d2";
        JPushClient jpushClient = new JPushClient(MASTER_SECRET, APP_KEY, null, ClientConfig.getInstance());

        // For push, all you need do is to build PushPayload object.
        PushPayload payload = buildPayload(msg);

        try {
            PushResult result = jpushClient.sendPush(payload);
            log.info("Got result - " + result);

        } catch (APIConnectionException e) {
            // Connection error, should retry later
            log.error("Connection error, should retry later", e);

        } catch (APIRequestException e) {
            // Should review the error, and fix the request
            log.error("Should review the error, and fix the request", e);
            log.info("HTTP Status: " + e.getStatus());
            log.info("Error Code: " + e.getErrorCode());
            log.info("Error Message: " + e.getErrorMessage());
        }
    }

    /**
     * 构建机关推送消息
     *
     * @param msg 消息
     * @return 极光消息
     */
    private static PushPayload buildPayload(String msg) {
        return PushPayload
                .newBuilder()
                .setPlatform(Platform.android())
                .setAudience(Audience.alias("17633052061"))
                .setNotification(Notification.alert(msg))
                .build();
    }

    private final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(50, 50, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>());

    @SneakyThrows
    private void calWrapper() {
        long start = System.currentTimeMillis();

        OkHttpClient okHttpClient = new OkHttpClient();
        Request request = new Request.Builder()
                .url("https://open.huobigroup.com/v1/common/currencys")
                .get()
                .build();
        Call call = okHttpClient.newCall(request);
        Response execute = call.execute();
        assert execute.body() != null;
        JSONObject jsonObject = JSON.parseObject(execute.body().string(), JSONObject.class);
        JSONArray data = jsonObject.getJSONArray("data");
        List<String> coins = new ArrayList<>();
        data.forEach(o -> coins.add((String) o));

        log.info(MessageFormat.format("火币平台当前币种数量 ==> {0}", coins.size()));

        // 获取货币一天的行情
        Map<String, List<CoinInfo>> dayInfo = getCoinInfo(coins, "1day", "1");

        // 保存货币计算的特征值
        List<CoinCal> coinCals = new ArrayList<>();

        // 按交易量进行过滤
        filterByVol(dayInfo, coinCals);

        log.info("交易量符合要求的货币数量 ==> " + coinCals.size());

        // 获取货币15分钟行情
        List<String> filterByVol = coinCals.stream().map(CoinCal::getSymbol).collect(Collectors.toList());
        Map<String, List<CoinInfo>> minuteInfo = getCoinInfo(filterByVol, "15min", "1");

        // 按15min增长量进行过滤
        filterByRate(minuteInfo, coinCals);

        long l = System.currentTimeMillis() - start;

        log.info("本次计算消耗时间 ==> " + l);

        // jpush推送到手机
        push2App(JSON.toJSONString(coinCals));
    }

    /**
     * 15分钟增长率小于5%的过滤掉
     *
     * @param minuteInfo 货币基础数据
     * @param coinCals   货币计算数据
     */
    private void filterByRate(Map<String, List<CoinInfo>> minuteInfo, List<CoinCal> coinCals) {
        Set<Map.Entry<String, List<CoinInfo>>> entries = minuteInfo.entrySet();
        for (Map.Entry<String, List<CoinInfo>> entry : entries) {
            List<CoinInfo> coinInfoList = entry.getValue();
            // 只查询一条行情数据
            CoinInfo coinInfo = coinInfoList.get(0);
            BigDecimal rate = calRate(coinInfo.getOpen(), coinInfo.getClose());

            log.info("{} => {} => {} => {}", coinInfo.getOpen(), coinInfo.getClose(), coinInfo.getSymbol(), rate.toString());

            if (rate.compareTo(new BigDecimal("0.05")) > 0) {
                Optional<CoinCal> first = coinCals.stream().filter(coinCal -> coinCal.getSymbol().equals(coinInfo.getSymbol())).findFirst();
                if (first.isPresent()) {
                    first.get().setMinuteRate(rate);
                } else {
                    log.info("xx");
                }
            }
        }

        coinCals.removeIf(next -> Objects.isNull(next.getMinuteRate()));
    }

    /**
     * 按交易量过滤火气
     *
     * @param dayInfo  货币基础数据
     * @param coinCals 保存过滤后的货币
     */
    private void filterByVol(Map<String, List<CoinInfo>> dayInfo, List<CoinCal> coinCals) {
        dayInfo.forEach((s, coinInfos) -> {
            // 获取一天的数据
            CoinInfo coinInfo = coinInfos.get(0);
            BigDecimal divide = calRate(coinInfo.getOpen(), coinInfo.getClose());
            BigDecimal vol = toDecimal(coinInfo.getVol());

            // 保存计算结果
            CoinCal coinCal = new CoinCal();
            coinCal.setRate(divide);
            coinCal.setVolume(vol);
            coinCal.setSymbol(coinInfo.getSymbol());
            coinCals.add(coinCal);
        });

        // 过滤出符合条件的货币
        Iterator<CoinCal> iterator = coinCals.iterator();
        while (iterator.hasNext()) {
            CoinCal next = iterator.next();
            int compareTo = next.getVolume().compareTo(new BigDecimal(1000000));
            // 不看日交易金额小于 100万的货币
            if (compareTo < 0) {
                iterator.remove();
                continue;
            }
            // 不看当日增长率小于 -10%的
            if (next.getRate().compareTo(new BigDecimal("-0.1")) < 0) {
                iterator.remove();
            }
        }
    }

    /**
     * 计算增长比率
     *
     * @param openStr  开盘价字符串
     * @param closeStr 收盘价字符串
     * @return 增长比率
     */
    private BigDecimal calRate(String openStr, String closeStr) {
        BigDecimal open = toDecimal(openStr);
        BigDecimal close = toDecimal(closeStr);
        if (open.compareTo(BigDecimal.ZERO) == 0) {
            return BigDecimal.ZERO;
        }
        return close.subtract(open).divide(open, 2, RoundingMode.HALF_UP);
    }

    /**
     * 币种行情查询
     *
     * @param coins  币种集合
     * @param period 1min, 1hour, 1day
     * @param size   每个币种查询时间段数量
     * @return 币种行情
     */
    @SneakyThrows
    private Map<String, List<CoinInfo>> getCoinInfo(List<String> coins, String period, String size) {
        Map<String, List<CoinInfo>> result = new HashMap<>();

        // 多线程调用接口进行查询
        List<Future<List<CoinInfo>>> futureList = new ArrayList<>();
        for (String coin : coins) {
            Future<List<CoinInfo>> submit = threadPoolExecutor.submit(() -> queryCoin(period, size, coin));
            futureList.add(submit);
        }
        futureList.forEach(listFuture -> addToMap(listFuture, result));
        return result;
    }

    @SneakyThrows
    private void addToMap(Future<List<CoinInfo>> future, Map<String, List<CoinInfo>> result) {
        List<CoinInfo> coinInfos = future.get();
        if (!CollectionUtils.isEmpty(coinInfos)) {
            CoinInfo coinInfo = coinInfos.get(0);
            result.put(coinInfo.getSymbol(), coinInfos);
        }
    }

    @SneakyThrows
    private List<CoinInfo> queryCoin(String period, String size, String coin) {
        OkHttpClient okHttpClient = new OkHttpClient();
        String url = MessageFormat.format("https://open.huobigroup.com/market/history/kline?symbol={0}usdt&period={1}&size={2}", coin, period, size);
        Request build = new Request.Builder()
                .url(url)
                .get()
                .build();
        Call call1 = okHttpClient.newCall(build);
        Response execute1 = call1.execute();
        assert execute1.body() != null;
        String string = execute1.body().string();
        log.info("本次接口获取到的加密货币数据 {}", string);
        JSONObject jsonObject1 = JSON.parseObject(string, JSONObject.class);
        JSONArray data1 = jsonObject1.getJSONArray("data");

        if (Objects.isNull(data1)) {
            return null;
        }

        List<CoinInfo> coinInfos = new ArrayList<>();
        for (Object o : data1) {
            // 即解析货币基础数据
            JSONObject o1 = (JSONObject) o;
            CoinInfo coinInfo = JSON.parseObject(o1.toJSONString(), CoinInfo.class);
            coinInfo.setSymbol(coin);
            coinInfos.add(coinInfo);
        }
        return coinInfos;
    }

    private BigDecimal toDecimal(String s) {
        return new BigDecimal(s).setScale(10, RoundingMode.HALF_UP);
    }
}
